package com.mhy.common.id;

import lombok.extern.slf4j.Slf4j;

/**
 * id格式
 * 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000
 * SnowFlake算法的核心思想：使用41bit作为毫秒数，10bit作为机器ID（5bit数据中心，5bit实例ID），12bit作为毫秒内流水号<br/>
 * 41bit时间戳（指代时间戳差值）=当前时间戳 - 初始时间戳<br/>
 * 41位时间戳 (1L << 41) / (1000L * 60 * 60 *24 * 365) = 69年(从初始时间算起，该算法可以使得69年，够用了)<br/>
 * 10bit作为机器ID 其中5bit数据中心，5bit实例ID（可以根据实际工作需要进行调整） 共计可以部署1024个实例<br/>
 * 12bit作为毫秒内的流水号0~4095(单实例指定毫秒下最多可以生成4096个id值)<br/>
 *
 * 基于上述算法规则：SnowFlake集群理论上最大每秒能生成1024*4096=4,194,304个id值，足以应付大多数工作场合了<br/>
 * PS:网上很多注释说是每秒最大400万个ID，但是41位时间戳是毫秒值，这个值存疑<br/>
 * 基于System.currentTimeMillis为获取毫秒值，1S内可以产出多个值<br/>
 * 优点:
 *  1. 整体上按时间自增排序，整个分布式系统内不会产生ID碰撞
 *  2. 生成ID不依赖于DB，完全内存生成，性能高
 *  3. ID趋势递增，索引性能相对较好
 * 缺点:
 *  1. 依赖于系统时钟的一致性。如果某台机器的系统时钟回拨，可能造成ID冲突或乱序
 *  2. ID长度非定长，不适合定长业务需求
 * @ClassName SnowFlake
 * @Description
 * @Author mhy2011@163.com
 * @Date 2020-04-12 20:56:00
 * @Versioin 1.0
 */
@Slf4j
public class SnowFlake {
    /**
     * 初始时间 这里使用了2020-01-01 00:00:00，算法足以支撑到2090年左右
     */
    private static final long START_TIME = 1577808000000L;
    /**
     * 单实例中序列占位数 12位，每个实例中指定毫秒下可以生成最多4096个id值
     */
    private static final long SEQUENCE_BITS = 12L;
    /**
     * 生成序列掩码，用于check序列是否溢出，溢出要等待
     * 如果不需要调整序列占位的话可以直接使用4095
     */
    private static final long SEQUENCE_MASK = -1L ^ (-1L << SEQUENCE_BITS);

    /**
     * 实例所占位数 此处设置了占用5位，可以调整
     */
    private static final long WORKER_BITS = 5L;
    /**
     * 支持最大实例ID,如果不想调整机器占位长度的话可以直接写31
     */
    private static final long MAX_WORKER_ID = -1L ^ (-1L << WORKER_BITS);
    /**
     * 实例向左移位数，用于拼接最终id使用，此处为12
     */
    private static final long WORKER_ID_SHIFT = SEQUENCE_BITS;

    /**
     * 数据中心所占位数 此处设置了占用5位，可以调整
     */
    private static final long DC_BITS = 5L;
    /**
     * 支持最大数据中心标识ID,如果不想调整数据中心占位长度的话可以直接写31
     */
    private static final long MAX_DC_ID = -1L ^ (-1L << DC_BITS);
    /**
     * 数据中心向左位移位数即序列所占位数+机器所占位数
     */
    private static final long DC_ID_SHIFT = SEQUENCE_BITS + WORKER_BITS;
    /**
     * 时间戳向左位移位数22（12 + 5 + 5），序列所占位数+机器所占位数+数据中心所占位数
     */
    private static final long TIMESTAMP_LEFT_SHIFT = SEQUENCE_BITS + WORKER_BITS + DC_BITS;

    private long dcId;  //数据中心0~31
    private long workerId;  //实例0~31
    private long sequence = 0L; //序列0~4095
    private long lastTimestamp = -1L;   //上次生成ID时的时间戳

    /**
     * 构造方法
     * @param dcId
     * @param workerId
     */
    public SnowFlake(long dcId, long workerId) {
        if (dcId > MAX_DC_ID || dcId < 0) {
            log.warn("dcId is invalid. allow value is 0~{}, but param value is {}", MAX_DC_ID, dcId);
            throw new IllegalArgumentException(
                    String.format("dcId is invalid. allow value is 0~%d, but param value is %d", MAX_DC_ID, dcId)
            );
        }
        if (workerId > MAX_DC_ID || workerId < 0) {
            log.warn("workerId is invalid. allow value is 0~{}, but param value is {}", MAX_WORKER_ID, workerId);
            throw new IllegalArgumentException(
                    String.format("workerId is invalid. allow value is 0~%d, but param value is %d", MAX_WORKER_ID, workerId)
            );
        }
        this.dcId = dcId;
        this.workerId = workerId;
    }

    /**
     * 获取一个ID值
     * @return snow flake id
     */
    public synchronized long getId() {

        long curTimestamp = System.currentTimeMillis();  //获取当前时间戳
        // 检查程序运行时系统时钟是否发生过回拨，如果有的话要进行异常报警
        if (curTimestamp < lastTimestamp) {
            long changeTime = lastTimestamp - curTimestamp;
            log.warn("System Clock moved backwards. changed time is {} milliseconds", changeTime);
            throw new RuntimeException(
                    String.format("System Clock moved backwards. changed time is %d milliseconds",changeTime));
        }

        // 如果时间相等，需要对序列号进行递增处理
        if (lastTimestamp == curTimestamp) {
            // 序列溢出 需要阻塞等待
            if (++sequence > SEQUENCE_MASK) {
                while (curTimestamp <= lastTimestamp) {
                    curTimestamp = System.currentTimeMillis();
                }
            }
        } else {    //时间戳发生了改变 序列号从新从0开始计算
            sequence = 0L;
            lastTimestamp = curTimestamp;   //变更最后一次生成ID时间
        }

        //通过移位+或 运算拼接组成64位的ID值
        return ((curTimestamp - START_TIME) << TIMESTAMP_LEFT_SHIFT)    //时间位左移22位
                | (dcId << DC_ID_SHIFT) //数据中心位左移17位
                | (workerId << WORKER_ID_SHIFT) //实例位左移12位
                | sequence; //序号号位不需要移动
    }

}
